use async_trait::async_trait;
use serde_json::Value;
use std::sync::Arc;

use my_rust_core::{
    constants::error,
    security::{
        id_field::IdField,
        jwt::{self, MyClaims, MyUserDetails, generate_token},
    },
    shared::{
        self,
        app_config_service::{self, APP_CONFIG},
    },
};

use super::{user_active_token_service::UserActiveTokenService, user_service::UserService};

#[async_trait]
pub trait AuthenticationService: Send + Sync {
    /// Authenticates user based on username and password.
    async fn authenticate_by_credential(
        &self,
        username: &str,
        password: &str,
        ip_address: &str,
        device_info: &Value,
        system_info: &Value,
    ) -> (MyUserDetails, String);

    /// Refreshes access token with valid refresh token.
    async fn refresh_token(
        &self,
        refresh_token: &str,
        ip_address: &str,
        device_info: &Value,
        system_info: &Value,
    ) -> (MyUserDetails, String);

    async fn generate_user_details(&self, username: &str) -> MyUserDetails;
}

pub struct AuthenticationServiceImpl {
    user_service: Arc<dyn UserService>,
    user_active_token_service: Arc<dyn UserActiveTokenService>,
}

impl AuthenticationServiceImpl {
    pub fn new(
        user_service: Arc<dyn UserService>,
        user_active_token_service: Arc<dyn UserActiveTokenService>,
    ) -> Self {
        AuthenticationServiceImpl {
            user_service,
            user_active_token_service,
        }
    }
}

#[async_trait]
impl AuthenticationService for AuthenticationServiceImpl {
    /// Authenticates user based on username and password.
    async fn authenticate_by_credential(
        &self,
        username: &str,
        password: &str,
        ip_address: &str,
        device_info: &Value,
        system_info: &Value,
    ) -> (MyUserDetails, String) {
        let mut my_user_details = MyUserDetails::default();
        let mut error_code = String::new();

        if self
            .user_service
            .validate_password(username, password)
            .await
        {
            let user_status = self.user_service.query_status(username).await.unwrap();

            // Checks on user each status.
            if !user_status.enabled {
                error_code = error::ERROR_USER_ACCOUNT_DISABLED.to_string();
            } else if user_status.account_expired {
                error_code = error::ERROR_USER_ACCOUNT_EXPIRED.to_string();
            } else if user_status.account_locked {
                error_code = error::ERROR_USER_ACCOUNT_LOCKED.to_string();
            } else if user_status.password_expired {
                error_code = error::ERROR_USER_PASSWORD_EXPIRED.to_string();
            } else {
                my_user_details = self.generate_user_details(username).await;

                self.user_active_token_service
                    .update_tokens(&my_user_details, ip_address, device_info, system_info)
                    .await
                    .unwrap();
            }
        } else {
            // If the user input is not correct.
            // Actually we did not give info like 'user not found' or 'password is incorrect'.
            // This is for void brute force attack. If the attacker does NOT know the login id or password is correct that would make the attach more difficult.
            error_code = error::ERROR_USER_ACCOUNT_BAD_CREDENTIAL.to_string();
        }

        (my_user_details, error_code)
    }

    /// Refreshes access token with valid refresh token.
    async fn refresh_token(
        &self,
        refresh_token: &str,
        ip_address: &str,
        device_info: &Value,
        system_info: &Value,
    ) -> (MyUserDetails, String) {
        let mut my_user_details = MyUserDetails::default();
        let mut error_code = String::new();

        let (claims, mut valid) = jwt::validate_token(
            &refresh_token,
            &app_config_service::APP_CONFIG.jwt.signing_key,
        );

        // If token is valid we still need to check if it is still active.
        if valid {
            valid = self
                .user_active_token_service
                .is_active_refresh_token(&claims.as_ref().unwrap().username, &refresh_token)
                .await
                .unwrap();
        }

        if valid {
            // Generates new details.
            my_user_details = self
                .generate_user_details(&claims.as_ref().unwrap().username)
                .await;

            // But keeps refresh token unchanged.
            my_user_details.refresh_token = refresh_token.to_owned();

            // Updates new token as active.
            self.user_active_token_service
                .update_tokens(&my_user_details, ip_address, device_info, system_info)
                .await
                .ok();
        } else {
            error_code = error::ERROR_USER_ACCOUNT_NOT_AUTHORIZED.to_owned();
        }

        (my_user_details, error_code)
    }

    /// Generates MyUserDetails based on username.
    async fn generate_user_details(&self, username: &str) -> MyUserDetails {
        let mut my_user_details = MyUserDetails::default();

        if !username.is_empty() {
            // Queries user details from db.
            let user_details = self.user_service.query_details(None, username).await;

            if let Some(user_details) = user_details {
                // For refresh token which does not need roles and it never expires.
                let mut claims = MyClaims {
                    id: IdField::Text(user_details.id.clone()),
                    username: username.to_owned(),
                    account_group_id: IdField::Text(user_details.account_group_id.clone()),
                    ..Default::default()
                };

                // Generates refresh token. 0 means it never expires.
                let refresh_token_string = generate_token(
                    &mut claims,
                    &APP_CONFIG.jwt.signing_key,
                    &APP_CONFIG.jwt.max_expiration,
                );

                // For access token which will expires in particular duration.
                claims.roles = user_details
                    .full_roles
                    .iter()
                    .map(|p| p.authority.clone())
                    .collect();

                // Generates access token.
                let access_token_string = generate_token(
                    &mut claims,
                    &APP_CONFIG.jwt.signing_key,
                    &APP_CONFIG.jwt.expiration,
                );

                // Prepares MyUserDetails data.
                my_user_details.account_group_id =
                    IdField::Text(user_details.account_group_id.clone());

                my_user_details.id = IdField::Text(user_details.id.clone());
                my_user_details.username = user_details.username;
                my_user_details.full_name = user_details.full_name;
                my_user_details.avatar_url = user_details.avatar_url;
                my_user_details.is_account_group_creator = user_details.is_account_group_creator;
                my_user_details.bearer = String::from("bearer");
                my_user_details.roles = claims.roles;
                my_user_details.full_roles = user_details.full_roles;
                my_user_details.access_token = access_token_string.unwrap();
                my_user_details.refresh_token = refresh_token_string.unwrap();

                my_user_details.generated_at =
                    shared::date_helper::from_timestamp_to_utc_string(claims.iat as i64);

                my_user_details.expired_at =
                    shared::date_helper::from_timestamp_to_utc_string(claims.exp as i64);

                my_user_details.expires_in = APP_CONFIG.jwt.expiration;
            }
        }

        my_user_details
    }
}
